package com.sgam.utils;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.log4j.Logger;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.support.AbstractMessageSource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * @author Serghey.Golub
 *
 */

public class DatabaseReloadableResourceBundleMessageSource extends AbstractMessageSource 	implements ResourceLoaderAware {
	protected final Logger log = Logger.getLogger(getClass());

		private static final String DEFAULT_TABLE_NAME = "REF_MESSAGES";
		/** The datasource used for accessing the database */
		private DataSource 			dataSource;
	    private String  		defaultEncoding;
	    private String      		defaultLocale; 

		/** The name of the table used in the database */
		private String basename;
		/** way to query the db */
		private JdbcTemplate jdbcTemplate;
		
		/** Cache to hold already loaded properties */
		private final Map<String, String> cachedProperties = new HashMap<String, String>();
		
		/** Milliseconds to hold the cash */
		private long cacheMillis = -1;
		/** reset everytime the timestamp was changed */
		private long refreshTimestamp = -1;

		/**
		 * The method that does the loading of all the properties from the database
		 * 
		 * @return properties
		 */
		private Map<String, String> refreshProperties() {
			synchronized (this.cachedProperties) {
				
				List<Map<String, Object>> list = (List<Map<String, Object>>) jdbcTemplate.queryForList("Select KEY as id, MD_VALUE, RU_VALUE, EN_VALUE from " + DEFAULT_TABLE_NAME);
				cachedProperties.clear();
				this.refreshTimestamp = System.currentTimeMillis();
				for (Map map : list) {
					cachedProperties.put("md_" + map.get("id").toString(), map.get("MD_VALUE").toString());
					cachedProperties.put("ru_" + map.get("id").toString(), map.get("RU_VALUE").toString());
					cachedProperties.put("en_" + map.get("id").toString(), map.get("EN_VALUE").toString());
				}
				return cachedProperties;
			}
		}
		
		private Map<String, String> getProperties() {
			synchronized (this.cachedProperties) {
				if (this.cachedProperties != null && 
						(refreshTimestamp < 0 || // do not use refresh
								refreshTimestamp >( System.currentTimeMillis() - this.cacheMillis)) // cashe is not expired
								) {
					// up to date
					log.debug("===========================================================");
					log.debug("               Cache is up to date                         ");
					log.debug("cachedProperties is empty ... " + this.cachedProperties.isEmpty() );
					log.debug("refreshTimestamp = " + refreshTimestamp );
					log.debug("System.currentTimeMillis() = " + System.currentTimeMillis());
					log.debug("this.cacheMillis = " +  this.cacheMillis );
					log.debug("System.currentTimeMillis() - this.cacheMillis) = " +  (System.currentTimeMillis() - this.cacheMillis));
					log.debug("===========================================================");
					return this.cachedProperties;
				}
				log.debug("===========================================================");
				log.debug("               Cache is expired                            ");
				log.debug("cachedProperties is empty ... " + this.cachedProperties.isEmpty() );
				log.debug("refreshTimestamp = " + refreshTimestamp );
				log.debug("System.currentTimeMillis() = " + System.currentTimeMillis());
				log.debug("this.cacheMillis = " +  this.cacheMillis );
				log.debug("System.currentTimeMillis() - this.cacheMillis) = " +  (System.currentTimeMillis() - this.cacheMillis));
				log.debug("===========================================================");
				return refreshProperties();
			}
		}

		public String getBasename() {
			return basename;
		}

		/**
		 * initializer for preventing dublicate code in the constructors
		 * 
		 * @param dataSource
		 * @param basename
		 */
		private void initialize(DataSource dataSource, String basename) {
			// this.dataSource = dataSource;
			this.basename = basename;
			this.jdbcTemplate = new JdbcTemplate(dataSource);
			setUseCodeAsDefaultMessage(true);
			log.debug(refreshProperties());
			log.debug("DatabaseMessageSource is constructed with tableName: " + basename);
 
			if (this.cacheMillis > 0) {
				log.debug("Refreshing is enabled and set to  " + this.cacheMillis/1000 + " sec. (" + this.cacheMillis + " msec)"); 
				this.refreshTimestamp = System.currentTimeMillis();
			}
		}

		
		public DatabaseReloadableResourceBundleMessageSource() {
			super();
			initialize(this.dataSource, DEFAULT_TABLE_NAME);
		}		
		
		/**
		 * <p>
		 * This constructor must get the dataSource from the spring
		 * applicationContext.
		 * </p>
		 * <p>
		 * the default table name will be used for accessing the correct table
		 * </p>
		 * 
		 * @param dataSource
		 *            The dataSource used by this class
		 */
		public DatabaseReloadableResourceBundleMessageSource(DataSource dataSource) {
			super();
			initialize(dataSource, DEFAULT_TABLE_NAME);
		}

		/**
		 * <p>
		 * This constructor provides the possibility of accessing a custom table
		 * from the given datasource.
		 * </p>
		 * 
		 * @param dataSource
		 * @param basename
		 */
		public DatabaseReloadableResourceBundleMessageSource(DataSource dataSource, String basename) {
			super();
			initialize(dataSource, basename);
		}

		/**
		 * This method is neede if we want to make use of the abstracht class we are
		 * extending from. The locale argument is given but in this implemention I
		 * will do nothing with it. The website I am currently building is in only 1
		 * language and that is in dutch.
		 */
		@Override
		public MessageFormat resolveCode(String code, Locale locale) {
			String msg = getProperties().get(locale +"_"+ code);
			MessageFormat result = createMessageFormat(msg, locale);
			return result;
		}

		@Override
		protected String resolveCodeWithoutArguments(String code, Locale locale) {
			return getProperties().get(locale +"_"+ code);
		}

		public void setResourceLoader(ResourceLoader arg0) {
		}

		/**
		 * Set the number of seconds to cache loaded properties .
		 * <ul>
		 * <li>Default is "-1", indicating to cache forever (just like
		 * <code>java.util.ResourceBundle</code>).
		 * <li>A positive number will cache loaded properties files for the given
		 * number of seconds. This is essentially the interval between refresh
		 * checks. Note that a refresh attempt will first check the last-modified
		 * timestamp of the file before actually reloading it; so if files don't
		 * change, this interval can be set rather low, as refresh attempts will not
		 * actually reload.
		 * <li>A value of "0" will check the last-modified timestamp of the file on
		 * every message access. <b>Do not use this in a production environment!</b>
		 * </ul>
		 */
		public void setCacheSeconds(int cacheSeconds) {
			this.cacheMillis = (cacheSeconds * 1000);
		}
		
		
		public DataSource getDataSource() {
			return dataSource;
		}

		public void setDataSource(DataSource dataSource) {
			this.dataSource = dataSource;
		}

		public String getDefaultEncoding() {
			return defaultEncoding;
		}

		public void setDefaultEncoding(String defaultEncoding) {
			this.defaultEncoding = defaultEncoding;
		}

		public String getDefaultLocale() {
			return defaultLocale;
		}

		public void setDefaultLocale(String defaultLocale) {
			this.defaultLocale = defaultLocale;
		}

		@Override
		public String toString() {
			StringBuilder str = new StringBuilder().append("\n");
			for (String msg : getProperties().keySet()) {
				str.append(msg);
				str.append(" = ");
				str.append(getProperties().get(msg));
				str.append("\n");
			}
			return str.toString();
		}
}